﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;

namespace BusLib
{
    public class Navigator
    {
        const double MaxWalkDistance = 0.75f;
        const double WalkSpeedMPH = 3.0; // 20 minutes/mile

        public enum State
        {
            Idle,
            Walking,
            Waiting,
            Riding
        };

        public struct PlanStep
        {
            public State doing;
            public DateTime startTime, endTime;
            public LatLong startPt, endPt;

            private Stop _startStop, _endStop;
            public Stop startStop 
            { 
                get { return _startStop; }
                set 
                { 
                    _startStop = value; 
                    if (value != null) 
                        startPt = value.Position;
                } 
            }
            public Stop endStop 
            { 
                get { return _endStop; } 
                set 
                { 
                    _endStop = value;
                    if (value != null)
                        endPt = value.Position; 
                }
            }

            public Trip trip;

            // Walk from Stop ss to es
            public PlanStep(DateTime st, Stop ss, Stop es)
            {
                doing = State.Walking;
                trip = null;
                startPt = ss.Position;
                endPt = es.Position;
                _startStop = ss;
                _endStop = es;
                startTime = st;
                endTime = st + TimeSpan.FromHours(GeoUtils.distance(startPt, endPt) / WalkSpeedMPH);
            }
            // Ride from Stop ss to es on Trip t
            public PlanStep(DateTime st, Trip t, Stop ss, Stop es)
            {
                doing = State.Riding;
                trip = t;
                startPt = ss.Position;
                endPt = es.Position;
                _startStop = ss;
                _endStop = es;

                startTime = BusUtils.ServiceDay(st) + t.GetStopTime(ss).departure_time;
                endTime = BusUtils.ServiceDay(st) + t.GetStopTime(es).arrival_time;
            }

            // Wait at stop s for trip t to arrive
            public PlanStep(DateTime st, Trip t, Stop s)
            {
                doing = State.Waiting;
                trip = t;
                startPt = endPt = s.Position;
                _startStop = _endStop = s;

                startTime = st;
                endTime = BusUtils.ServiceDay(st) + t.GetStopTime(s).departure_time;

            }

            // Walk from a point to a stop
            public PlanStep(DateTime st, LatLong p, Stop s)
            {
                doing = State.Walking;
                trip = null;
                startPt = p;
                endPt = s.Position;
                _startStop = null;
                _endStop = s;

                startTime = st;
                endTime = st + TimeSpan.FromHours(GeoUtils.distance(startPt, endPt) / WalkSpeedMPH);
            }

            // Walk to your destination:
            public PlanStep(DateTime st, Stop s, LatLong p)
            {
                doing = State.Walking;
                trip = null;
                startPt = s.Position;
                endPt = p;
                _startStop = s;
                _endStop = null;

                startTime = st;
                endTime = st + TimeSpan.FromHours(GeoUtils.distance(startPt, endPt) / WalkSpeedMPH);
            }

            public LatLong[] GetShapePoints()
            {

                switch(doing)
                {
                case State.Walking:
                    List<LatLong> pts = new List<LatLong>();
                    pts.Add(startPt);
                    pts.Add(endPt);
                    return pts.ToArray();
                case State.Riding:
                    return trip.GetPartialShape(startStop, endStop);
                default:
                    return new LatLong[0];
                }
            }
        };

        public class Plan : List<PlanStep>
        {
            public Plan() {}
            public Plan(Plan p): base(p) {}

            public DateTime startTime { get {return base[0].startTime; } }
            public DateTime endTime { get { return base[Count-1].endTime; } }
        }

        public DateTime startTime, endTime;
        public LatLong startPt, endPt, lastKnownPt;
        public State state;
        public Trip currentlyRiding;
        public List<Plan> plans;
        public Plan plan;

        public /*private*/ Stop[] startStops;
        public /*private*/ Stop[] endStops;

        public Navigator()
        {
            state = State.Idle;
            currentlyRiding = null;
            
            // Test:
            startPt.latitude = 47.66902; startPt.longitude = -122.11855;
            endPt.latitude = 47.66225; endPt.longitude = -122.33369;

            lastKnownPt = startPt;

            startStops = Stop.FindStopsNear(startPt, MaxWalkDistance);
            endStops = Stop.FindStopsNear(endPt, MaxWalkDistance);

            startTime = DateTime.Parse("11/18/2009 17:00:00");
        }

        private List<Plan> GetNextMoves(Plan p)
        {
            List<Plan> newPlans = new List<Plan>();
            PlanStep lastStep = p.Last();

            switch (lastStep.doing)
            {
            case State.Riding:
                // We just got off the bus...

                // Are we there, yet?
                if (endStops.Contains(lastStep.endStop))
                {
                    // Yay!  This trip is basically done.
                    Plan newP = new Plan(p);
                    newP.Add(new PlanStep(lastStep.endTime, p.Last().endStop, endPt));

                    if (plan == null || plan.endTime > newP.endTime)
                    {
                        plan = newP;
                        // Filter all partial plans where the end-time is after our current best:
                        plans = plans.Where(partial_plan => partial_plan.endTime > newP.endTime).ToList();
                    }
                    return newPlans;
                }

                // What nearby stops can we walk to?
                foreach (Stop s in Stop.FindStopsNear(lastStep.endPt, MaxWalkDistance))
                {
                    // Don't walk to the same stop
                    if (s != p.Last().endStop)
                    {
                        Plan newP = new Plan(p);
                        newP.Add(new PlanStep(lastStep.endTime, p.Last().endStop, s));
                        newPlans.Add(newP);
                    }
                }

                // Fall-through to the next case:
                goto case State.Walking;
            case State.Walking:
                // We just rode/walked to a stop.  What buses can we catch?

                // What buses at this stop can we wait for?
                foreach (Stop_Time t in lastStep.endStop.GetNextTrips(lastStep.endTime))
                {
                    if (p.Any(ps => ps.trip != null && ps.trip.Route == t.Trip.Route))
                        continue; // Don't get off & back on the same bus, or reverse directions
                    // XXX: exception for somehow shortcutting enough to get a bus ahead

                    if (t.Trip.Route.route_short_name != "44"
                        && t.Trip.Route.route_short_name != "43"
                        && t.Trip.Route.route_short_name != "48"
                        && t.Trip.Route.route_short_name != "271"
                        && t.Trip.Route.route_short_name != "545")
                        continue; // TEST

                    foreach (Stop_Time nextStop in t.GetNextStops())
                    {
                        // Consider getting off at each stop
                        Plan newP = new Plan(p);
                        newP.Add(lastStep = new PlanStep(lastStep.endTime, t.Trip, t.Stop));  // Wait for the bus
                        newP.Add(new PlanStep(lastStep.endTime, t.Trip, t.Stop, nextStop.Stop)); // Ride it to another stop
                        newPlans.Add(newP);
                    }
                }

                break;
            }

            return newPlans;
        }

        public void RecalculatePlan()
        {
            plans = new List<Plan>();
            // Seed the potential first steps of the plan:
            // Walking from the start point to each of the nearby stops.
            foreach (Stop s in startStops)
            {
                Plan p = new Plan();
                p.Add(new PlanStep(startTime, startPt, s));          // Walk to the stop
                plans.Add(p);
            }

            const int maxSteps = 5;
            for (int i = 0; i < maxSteps; i++)
            {
                List<Plan> newPlans = new List<Plan>();

                foreach (Plan p in plans)
                {
                    List<Plan> newPlansForThisPlan = GetNextMoves(p);

                    newPlans.AddRange(newPlansForThisPlan);
                }

                plans = newPlans;
            }

//             if (plan != null)
//             {
//                 foreach (PlanStep ps in plan)
//                 {
//                     if (ps.trip != null)
//                         ps.trip.ComputeStopDistances();
//                 }
//             }

//            Trip.GetById(13149525).ComputeStopDistances();
//             Trip.GetById(12544536).ComputeStopDistances();
//             Trip.GetById(12541090).ComputeStopDistances();
//             PlanStep lastStep = new PlanStep(startTime, startPt, Stop.GetById(72456)); // Walk to the 545 stop @ RTC
//             plan = new Plan();
//             plan.Add(lastStep);
//             plan.Add(lastStep = new PlanStep(lastStep.endTime, Trip.GetById(13149525), Stop.GetById(72456))); // Wait for the 545
//             plan.Add(lastStep = new PlanStep(lastStep.endTime, Trip.GetById(13149525), Stop.GetById(72456), Stop.GetById(71344))); // Ride the 545
//             plan.Add(lastStep = new PlanStep(lastStep.endTime, Stop.GetById(71344), Stop.GetById(25751))); // Walk to the 43 stop @ Montlake
//             plan.Add(lastStep = new PlanStep(lastStep.endTime, Trip.GetById(12544536), Stop.GetById(25751))); // Wait for the 43
//             plan.Add(lastStep = new PlanStep(lastStep.endTime, Trip.GetById(12544536), Stop.GetById(25751), Stop.GetById(11354))); // 43 -> med center
//             plan.Add(lastStep = new PlanStep(lastStep.endTime, Trip.GetById(12541090), Stop.GetById(11354))); // Wait for 44
//             plan.Add(lastStep = new PlanStep(lastStep.endTime, Trip.GetById(12541090), Stop.GetById(11354), Stop.GetById(17310))); // Ride to 45th & Burke
//             plan.Add(lastStep = new PlanStep(lastStep.endTime, Stop.GetById(17310), endPt)); // Walk home
        }
    }
}
